local function ToggleOffPhysics(inst)
    inst.sg.statemem.isphysicstoggle = true
    inst.Physics:SetCollisionMask(COLLISION.GROUND)
end

local function ToggleOnPhysics(inst)
    inst.sg.statemem.isphysicstoggle = nil
    inst.Physics:SetCollisionMask(
        COLLISION.WORLD,
        COLLISION.OBSTACLES,
        COLLISION.SMALLOBSTACLES,
        COLLISION.CHARACTERS,
        COLLISION.GIANTS
    )
end

AddStategraphState("wilson",
    State {
        name = "bboy_jumpin",
        tags = { "doing", "busy", "canrotate", "nopredict", "nomorph" },

        onenter = function(inst, data)
            ToggleOffPhysics(inst)                                              -- 关闭物理引擎
            inst.components.locomotor:Stop()                                    -- 停止行走

            inst.sg.statemem.target = data.teleporter                           -- 起点传送器
            inst.sg.statemem.heavy = inst.components.inventory:IsHeavyLifting() -- 背着东西

            local pos = nil
            if data.teleporter ~= nil and data.teleporter.components.bboy_travelable ~= nil then
                pos = data.teleporter:GetPosition()
            end
            inst.sg.statemem.teleporterexit = data.teleporterexit -- 目标传送器:必须有,官方那边的说可以没有,咱们必须有

            -- 执行动作
            inst.AnimState:PlayAnimation(inst.sg.statemem.heavy and "heavy_jump" or "jump")

            -- 一些变量
            local MAX_JUMPIN_DIST = 3
            local MAX_JUMPIN_DIST_SQ = MAX_JUMPIN_DIST * MAX_JUMPIN_DIST
            local MAX_JUMPIN_SPEED = 6

            -- 朝着起点传送器跳跃
            local dist
            if pos ~= nil then
                inst:ForceFacePoint(pos:Get())
                local distsq = inst:GetDistanceSqToPoint(pos:Get())
                if distsq <= .25 * .25 then
                    dist = 0
                    inst.sg.statemem.speed = 0
                elseif distsq >= MAX_JUMPIN_DIST_SQ then
                    dist = MAX_JUMPIN_DIST
                    inst.sg.statemem.speed = MAX_JUMPIN_SPEED
                else
                    dist = math.sqrt(distsq)
                    inst.sg.statemem.speed = MAX_JUMPIN_SPEED * dist / MAX_JUMPIN_DIST
                end
            else
                inst.sg.statemem.speed = 0
                dist = 0
            end

            inst.Physics:SetMotorVel(inst.sg.statemem.speed * .5, 0, 0)

            -- inst.sg.statemem.teleportarrivestate = "bboy_jumpout" -- this can be overriden in the teleporter component
        end,

        timeline =
        {
            -- 维持官方默认的即可
            TimeEvent(.5 * FRAMES, function(inst)
                inst.Physics:SetMotorVel(inst.sg.statemem.speed * (inst.sg.statemem.heavy and .55 or .75), 0, 0)
            end),
            TimeEvent(1 * FRAMES, function(inst)
                inst.Physics:SetMotorVel(inst.sg.statemem.heavy and inst.sg.statemem.speed * .6 or inst.sg.statemem.speed, 0, 0)
            end),

            -- NORMAL WHOOSH SOUND GOES HERE
            TimeEvent(1 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    -- print ("START NORMAL JUMPING SOUND")
                    inst.SoundEmitter:PlaySound("wanda1/wanda/jump_whoosh")
                end
            end),

            -- HEAVY WHOOSH SOUND GOES HERE
            TimeEvent(5 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    -- print ("START HEAVY JUMPING SOUND")
                    inst.SoundEmitter:PlaySound("wanda1/wanda/jump_whoosh")
                end
            end),

            -- Heavy lifting
            TimeEvent(12 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(inst.sg.statemem.speed * .5, 0, 0)
                end
            end),
            TimeEvent(13 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(inst.sg.statemem.speed * .4, 0, 0)
                end
            end),
            TimeEvent(14 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(inst.sg.statemem.speed * .3, 0, 0)
                end
            end),

            -- Normal
            TimeEvent(15 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:Stop()
                end

                -- 仅对原版虫洞,进入原版虫洞之前播放,代码留着不影响
                if inst.sg.statemem.target ~= nil then
                    if inst.sg.statemem.target:IsValid() then
                        inst.sg.statemem.target:PushEvent("starttravelsound", inst)
                    else
                        inst.sg.statemem.target = nil
                    end
                end
            end),

            -- Heavy lifting
            TimeEvent(20 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:Stop()
                end
            end),
        },

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    local should_teleport = false
                    if inst.sg.statemem.target ~= nil and inst.sg.statemem.target:IsValid()
                        and inst.sg.statemem.target.components.bboy_travelable ~= nil then
                        local teleporterexit = inst.sg.statemem.teleporterexit
                        if teleporterexit then
                            inst.sg.statemem.target.components.bboy_travelable:Teleport(inst, teleporterexit)
                            should_teleport = true
                        end
                    end
                    if should_teleport then
                        inst.sg.statemem.isteleporting = true
                        inst.components.health:SetInvincible(true)
                        if inst.components.playercontroller ~= nil then
                            inst.components.playercontroller:Enable(false)
                        end
                        inst:Hide()
                        inst.DynamicShadow:Enable(false)
                        return
                    end
                    inst.sg:GoToState("bboy_jumpout")
                end
            end),
        },

        onexit = function(inst)
            if inst.sg.statemem.isphysicstoggle then
                ToggleOnPhysics(inst)
            end
            inst.Physics:Stop()

            if inst.sg.statemem.isteleporting then
                inst.components.health:SetInvincible(false)
                if inst.components.playercontroller ~= nil then
                    inst.components.playercontroller:Enable(true)
                end
                inst:Show()
                inst.DynamicShadow:Enable(true)
            end
        end,
    }
)

AddStategraphState("wilson",
    State {
        name = "bboy_jumpout",
        tags = { "doing", "busy", "canrotate", "nopredict", "nomorph" },

        onenter = function(inst)
            ToggleOffPhysics(inst)
            inst.components.locomotor:Stop()

            inst.sg.statemem.heavy = inst.components.inventory:IsHeavyLifting()

            inst.AnimState:PlayAnimation(inst.sg.statemem.heavy and "heavy_jumpout" or "jumpout")

            inst.Physics:SetMotorVel(4, 0, 0)
        end,

        timeline =
        {
            -- Heavy lifting
            TimeEvent(4 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(3, 0, 0)
                end
            end),
            TimeEvent(12 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(2, 0, 0)
                end
            end),
            TimeEvent(12.2 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    if inst.sg.statemem.isphysicstoggle then
                        ToggleOnPhysics(inst)
                    end
                    inst.SoundEmitter:PlaySound("dontstarve/movement/bodyfall_dirt")
                end
            end),
            TimeEvent(16 * FRAMES, function(inst)
                if inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(1, 0, 0)
                end
            end),

            -- Normal
            TimeEvent(10 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(3, 0, 0)
                end
            end),
            TimeEvent(15 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(2, 0, 0)
                end
            end),
            TimeEvent(15.2 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    if inst.sg.statemem.isphysicstoggle then
                        ToggleOnPhysics(inst)
                    end
                    inst.SoundEmitter:PlaySound("dontstarve/movement/bodyfall_dirt")
                end
            end),

            TimeEvent(17 * FRAMES, function(inst)
                inst.Physics:SetMotorVel(inst.sg.statemem.heavy and .5 or 1, 0, 0)
            end),
            TimeEvent(18 * FRAMES, function(inst)
                inst.Physics:Stop()
            end),
        },

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("idle")
                end
            end),
        },

        onexit = function(inst)
            if inst.sg.statemem.isphysicstoggle then
                ToggleOnPhysics(inst)
            end
        end,
    }
)

AddStategraphState("wilson",
    State {
        name = "bboy_book_peruse",
        tags = { "doing" },

        onenter = function(inst)
            inst.components.locomotor:Stop()
            inst.AnimState:AddOverrideBuild("player_actions_bboy_read")
            inst.AnimState:PlayAnimation("action_uniqueitem_pre")
            inst.AnimState:PushAnimation("bboy_read", false)

            local book = inst.bufferedaction ~= nil and (inst.bufferedaction.target or inst.bufferedaction.invobject) or nil
            if book ~= nil then
                inst.components.inventory:ReturnActiveActionItem(book)

                local swap_build = book.swap_build
                local swap_prefix = book.swap_prefix or "book"
                local skin_build = book:GetSkinBuild()
                if skin_build ~= nil then
                    inst.AnimState:OverrideItemSkinSymbol("book_peruse", skin_build, "book_peruse", book.GUID, swap_build or "player_actions_bboy_read", swap_prefix .. "_peruse")
                    inst.sg.statemem.symbolsoverridden = true
                elseif swap_build ~= nil then
                    inst.AnimState:OverrideSymbol("book_peruse", swap_build, swap_prefix .. "_peruse")
                    inst.sg.statemem.symbolsoverridden = true
                end
            end
        end,

        timeline =
        {
            TimeEvent(25 * FRAMES, function(inst)
                inst.SoundEmitter:PlaySound("dontstarve/common/use_book")
            end),
            TimeEvent(68 * FRAMES, function(inst)
                inst.SoundEmitter:PlaySound("dontstarve/characters/actions/page_turn")
            end),
            TimeEvent(98 * FRAMES, function(inst)
                inst:PerformBufferedAction()
            end),
        },
        events =
        {
            EventHandler("animqueueover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("idle")
                end
            end),
        },

        onexit = function(inst)
            if inst.sg.statemem.symbolsoverridden then
                inst.AnimState:OverrideSymbol("book_peruse", "player_actions_bboy_read", "book_peruse")
                inst.AnimState:ClearOverrideBuild("player_actions_bboy_read")
            end
        end,
    }
)

-- 不能没有客户端的部分,否则玩家开启延迟补偿,读书会不执行动作
local TIMEOUT = 2
AddStategraphState("wilson_client",
    State {
        name = "bboy_book_peruse",
        tags = { "doing" },
        server_states = { "bboy_book_peruse" },

        onenter = function(inst)
            inst.components.locomotor:Stop()
            inst.AnimState:PlayAnimation("action_uniqueitem_pre")
            inst.AnimState:PushAnimation("action_uniqueitem_lag", false)

            inst:PerformPreviewBufferedAction()
            inst.sg:SetTimeout(TIMEOUT)
        end,

        onupdate = function(inst)
            if inst.sg:ServerStateMatches() then
                if inst.entity:FlattenMovementPrediction() then
                    inst.sg:GoToState("idle", "noanim")
                end
            elseif inst.bufferedaction == nil then
                inst.AnimState:PlayAnimation("book")
                inst.AnimState:SetFrame(72)
                inst.sg:GoToState("idle", true)
            end
        end,

        ontimeout = function(inst)
            inst:ClearBufferedAction()
            inst.AnimState:PlayAnimation("book")
            inst.AnimState:SetFrame(72)
            inst.sg:GoToState("idle", true)
        end,
    }
)
